package com.sky.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.RedisListenerConstant;
import com.sky.context.BaseContext;
import com.sky.dto.*;
import com.sky.entity.*;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.OrderBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.*;
import com.sky.result.PageResult;
import com.sky.server.WebSocketServer;
import com.sky.service.OrderService;
import com.sky.utils.HttpClientUtil;
import com.sky.utils.WeChatPayUtil;
import com.sky.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WeChatPayUtil weChatPayUtil;
    @Autowired
    private RedisTemplate redisTemplate;


    @Value("${sky.shop.address}")
    private String shopAddress;
    @Value("${sky.baidu.ak}")
    private String ak;

    private static final String geocodingURL = "https://api.map.baidu.com/geocoding/v3/?";

    private static final String ridingDirectionURL = "https://api.map.baidu.com/directionlite/v1/riding?";

    private static final Integer CANCEL_INTERVAL_MINUTE = 1;

    private static final Integer ADDRESS_RANGE = 5000; //单位m
    @Autowired
    private WebSocketServer webSocketServer;

    @Override
    @Transactional
    public OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO) {
        //处理业务异常（地址簿为空，购物车为空）
        //通过地址簿id得到的这个地址是此刻下单时的地址，将这个地址的信息保存到订单表中
        //即便后续修改了这个地址簿id的地址，也不会影响订单表中的地址信息
        AddressBook address = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if (address == null){
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }
        //优化用户下单功能，加入校验逻辑，如果用户的收货地址距离商家门店超出配送范围（配送范围为5公里内），则下单失败。
        StringBuilder detailAddress = new StringBuilder();
        String orderAddress = detailAddress.append(address.getDistrictName()).append(address.getDetail()).toString();
        String detailOrderAddress = address.getCityName() + orderAddress;
        Integer rideDistance = distance(detailOrderAddress, shopAddress);
        if (rideDistance > ADDRESS_RANGE) {
            throw new OrderBusinessException(MessageConstant.ADDRESS_OUT_OF_RANGE);
        }
        List<ShoppingCart> shoppingCarts = shoppingCartMapper.list(ShoppingCart.builder().userId(BaseContext.getCurrentId()).build());
        if (shoppingCarts == null || shoppingCarts.isEmpty()){
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }
        //向订单表插入1条数据
        Orders order = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO,order);
        order.setOrderTime(LocalDateTime.now());
        order.setPayStatus(Orders.UN_PAID);
        order.setStatus(Orders.PENDING_PAYMENT);
        order.setNumber(String.valueOf(System.currentTimeMillis()));
        order.setPhone(address.getPhone());
        order.setConsignee(address.getConsignee());
        order.setUserId(BaseContext.getCurrentId());
        order.setUserName(userMapper.getById(BaseContext.getCurrentId()).getName());
        //通常只用保存街道和具体地址
        order.setAddress(orderAddress);
        orderMapper.insert(order);
        //向订单明细表插入n条数据，将购物车里的商品添加到订单明细表中
        List<OrderDetail> orderDetails = shoppingCarts.stream().map(shoppingCart -> {
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(shoppingCart, orderDetail);
            orderDetail.setOrderId(order.getId());
            return orderDetail;
        }).collect(Collectors.toList());
        orderDetailMapper.insertBatch(orderDetails);
        //用redis实现订单超时自动取消，设置redis过期键
        log.info("设置redis过期键{}", RedisListenerConstant.UNPAID_EXPIRED_KEY+order.getId());
        redisTemplate.opsForValue().set(RedisListenerConstant.UNPAID_EXPIRED_KEY+order.getId(), order.getId(), CANCEL_INTERVAL_MINUTE, TimeUnit.MINUTES);
        //清空购物车
        shoppingCartMapper.deleteAllByUserId(BaseContext.getCurrentId());
        //来单提醒。按理来说应该放在支付成功后，但是跳过了支付功能。
        Map webSocketMap = new HashMap();
        webSocketMap.put(webSocketServer.TYPE, webSocketServer.SUBMIT_ORDER);
        webSocketMap.put(webSocketServer.ORDER_ID, order.getId());
        webSocketMap.put(webSocketServer.CONTENT, "订单号:"+order.getNumber());
        webSocketServer.sendToAllClient(JSON.toJSONString(webSocketMap));

        return OrderSubmitVO.builder().id(order.getId()).orderTime(order.getOrderTime()).orderAmount(order.getAmount()).orderNumber(order.getNumber()).build();
    }

    /**
     * 根据两地经纬度计算骑行距离
     * @param detailOrderAddress
     * @param shopAddress
     * @return
     */
    private Integer distance(String detailOrderAddress, String shopAddress) {
        Double[] shopLngAndLat = getLngAndLat(shopAddress, ak);
        Double[] orderLngAndLat = getLngAndLat(detailOrderAddress, ak);
        Map<String, String> ridingDirectionRequestParam = new HashMap<>();
        ridingDirectionRequestParam.put("ak", ak);
        ridingDirectionRequestParam.put("origin", shopLngAndLat[1] + "," + shopLngAndLat[0]);
        ridingDirectionRequestParam.put("destination", orderLngAndLat[1] + "," + orderLngAndLat[0]);
        ridingDirectionRequestParam.put("riding_type", "1");
        JSONObject response = JSONObject.parseObject(HttpClientUtil.doGet(ridingDirectionURL, ridingDirectionRequestParam));
        log.info("路线规划结果：{}", response);
        if (!response.getString("status").equals("0")){
            throw new OrderBusinessException("路线规划失败");
        }
        JSONObject distanceResult = response.getJSONObject("result");
        JSONArray routes = (JSONArray) distanceResult.get("routes");
        Integer distance = routes.getJSONObject(0).getInteger("distance");
        log.info("距离为：{}m", distance);
        return distance;
    }

    /**
     * 根据地址获取经纬度
     * @param address
     * @return
     */
    private Double[] getLngAndLat(String address, String ak) {
        Map<String, String> requestParam = new HashMap<>();
        requestParam.put("address", address);
        requestParam.put("output", "json");
        requestParam.put("ak", ak);
        JSONObject response = JSONObject.parseObject(HttpClientUtil.doGet(geocodingURL, requestParam));
        if (!response.getString("status").equals("0")){
            throw new OrderBusinessException("地址解析失败");
        }
        JSONObject lngAndLat = response.getJSONObject("result").getJSONObject("location");
        Double[] result = new Double[]{lngAndLat.getDouble("lng"), lngAndLat.getDouble("lat")};
        log.info("{}经纬度分别为{}", address, result);
        return result;
    }


    /**
     * 订单支付
     *
     * @param ordersPaymentDTO
     * @return
     */
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.getById(userId);

        //调用微信支付接口，生成预支付交易单
        JSONObject jsonObject = weChatPayUtil.pay(
                ordersPaymentDTO.getOrderNumber(), //商户订单号
                new BigDecimal(0.01), //支付金额，单位 元
                "苍穹外卖订单", //商品描述
                user.getOpenid() //微信用户的openid
        );

        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
            throw new OrderBusinessException("该订单已支付");
        }

        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));

        return vo;
    }

    /**
     * 支付成功，修改订单状态
     *
     * @param outTradeNo
     */
    public void paySuccess(String outTradeNo) {

        // 根据订单号查询订单
        Orders ordersDB = orderMapper.getByNumber(outTradeNo);

        // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
        Orders orders = Orders.builder()
                .id(ordersDB.getId())
                .status(Orders.TO_BE_CONFIRMED)
                .payStatus(Orders.PAID)
                .checkoutTime(LocalDateTime.now())
                .build();

        orderMapper.update(orders);
    }

    @Override
    public PageResult queryPage(Integer page, Integer pageSize, Integer status) {
        //先更新订单状态，如果订单未支付且超过支付时间，自动取消
        //Integer updateNumber = orderMapper.updateStatus(BaseContext.getCurrentId() ,Orders.PENDING_PAYMENT, Orders.CANCELLED, CANCEL_INTERVAL_MINUTE, "支付超时", true);
        //log.info("{}个订单支付超时，自动取消",updateNumber);
        PageHelper.startPage(page,pageSize);
        OrdersPageQueryDTO ordersPageQueryDTO = OrdersPageQueryDTO.builder().status(status).userId(BaseContext.getCurrentId()).build();
        Page<OrderVO> orderVoList = (Page<OrderVO>) orderMapper.list(ordersPageQueryDTO);
        //对每个订单查询订单详细信息，并进行封装
        orderVoList.forEach(orderVO -> {
            List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(orderVO.getId());
            orderVO.setOrderDetailList(orderDetails);
        });
        return new PageResult(orderVoList.getTotal(),orderVoList.getResult());
    }

    @Override
    public OrderVO getWithDetailById(Long id) {
        return orderMapper.getWithDetailsById(id);
    }

    @Override
    @Transactional
    public void cancel(Long id) throws Exception {
        //- 待支付1和待接单2状态下，用户可直接取消订单
        //- 商家已接单3状态下，用户取消订单需电话沟通商家
        //- 派送中4状态下，用户取消订单需电话沟通商家
        //- 如果在待接单2状态下取消订单，需要给用户退款
        //- 取消订单后需要将订单状态修改为“已取消”
        Orders order = orderMapper.getById(id);
        if (order == null){
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        Integer status = order.getStatus();
        if (!Objects.equals(status, Orders.PENDING_PAYMENT) && !Objects.equals(status, Orders.TO_BE_CONFIRMED)){
            //不是待支付和待接单状态，需要电话联系商家
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        if (Objects.equals(status, Orders.TO_BE_CONFIRMED)){
            //待接单状态，需要给用户退款
            weChatPayUtil.refund(order.getNumber(), order.getNumber(), order.getAmount(), order.getAmount());
            order.setPayStatus(Orders.REFUND);
        }
        order.setStatus(Orders.CANCELLED);
        order.setCancelReason("用户取消");
        order.setCancelTime(LocalDateTime.now());
        orderMapper.update(order);
    }

    @Override
    public void repetition(Long id) {
        List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(id);
        List<ShoppingCart> shoppingCartList = orderDetails.stream().map(orderDetail -> {
            ShoppingCart shoppingCart = new ShoppingCart();
            BeanUtils.copyProperties(orderDetail, shoppingCart);
            shoppingCart.setUserId(BaseContext.getCurrentId());
            shoppingCart.setCreateTime(LocalDateTime.now());
            return shoppingCart;
        }).collect(Collectors.toList());
        shoppingCartMapper.insertBatch(shoppingCartList);
    }

    @Override
    public PageResult queryPageForAdmin(OrdersPageQueryDTO ordersPageQueryDTO) {
        //Integer updateNumber = orderMapper.updateStatus(null, Orders.PENDING_PAYMENT, Orders.CANCELLED, CANCEL_INTERVAL_MINUTE, "支付超时", true);
        //log.info("{}个订单支付超时，自动取消",updateNumber);
        PageHelper.startPage(ordersPageQueryDTO.getPage(), ordersPageQueryDTO.getPageSize());
        //这是管理端，要查询所有订单，不用指定下单人id
        Page<OrderVO> orderVOS = (Page<OrderVO>) orderMapper.list(ordersPageQueryDTO);
        PageResult pageResult = new PageResult(orderVOS.getTotal(), null);

        //将每个orderVO转成orderVOForAdmin
        List<OrderVOForAdmin> pageResultList = orderVOS.stream().map(orderVO -> {
            OrderVOForAdmin orderVOForAdmin = new OrderVOForAdmin();
            BeanUtils.copyProperties(orderVO, orderVOForAdmin);
            //将当前订单的订单详情转成字符串
            List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(orderVO.getId());

            String orderDishes = orderDetails.stream().map(item -> {
                StringBuilder sb = new StringBuilder();
                sb.append(item.getName());
                sb.append("×");
                sb.append(item.getNumber());
                sb.append("; ");
                return sb.toString();
            }).collect(Collectors.joining());
            orderVOForAdmin.setOrderDishes(orderDishes);
            return orderVOForAdmin;
        }).collect(Collectors.toList());
        pageResult.setRecords(pageResultList);
        return pageResult;
    }

    @Override
    public OrderStatisticsVO statistics() {
        Integer toBeConfirmed = orderMapper.countByStatus(Orders.TO_BE_CONFIRMED);
        Integer confirmed = orderMapper.countByStatus(Orders.CONFIRMED);
        Integer deliveryInProgress = orderMapper.countByStatus(Orders.DELIVERY_IN_PROGRESS);
        return new OrderStatisticsVO(toBeConfirmed, confirmed, deliveryInProgress);
    }

    @Override
    public void confirm(OrdersConfirmDTO ordersConfirmDTO) {
        Orders order = Orders.builder().id(ordersConfirmDTO.getId()).status(Orders.CONFIRMED).build();
        orderMapper.update(order);
    }

    @Override
    public void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception {
        //- 商家拒单其实就是将订单状态修改为“已取消”
        //- 只有订单处于“待接单”状态时可以执行拒单操作
        //- 商家拒单时需要指定拒单原因
        //- 商家拒单时，如果用户已经完成了支付，需要为用户退款
        Orders order = orderMapper.getById(ordersRejectionDTO.getId());
        if (order == null || !Objects.equals(order.getStatus(), Orders.TO_BE_CONFIRMED)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        if (Objects.equals(order.getPayStatus(), Orders.PAID)){
            //String refund = weChatPayUtil.refund(order.getNumber(), order.getNumber(), order.getAmount(), order.getAmount());
            String refund = String.valueOf(order.getAmount());
            order.setPayStatus(Orders.REFUND);
            log.info("退款:{}", refund);
        }
        order.setStatus(Orders.CANCELLED);
        order.setRejectionReason(ordersRejectionDTO.getRejectionReason());
        order.setCancelTime(LocalDateTime.now());
        orderMapper.update(order);
    }

    @Override
    public void cancelByAdmin(OrdersCancelDTO ordersCancelDTO) throws Exception {
        //- 取消订单其实就是将订单状态修改为“已取消”
        //- 已完成的订单不能取消
        //- 商家取消订单时需要指定取消原因
        //- 商家取消订单时，如果用户已经完成了支付，需要为用户退款
        Orders order = orderMapper.getById(ordersCancelDTO.getId());
        if (order == null || Objects.equals(order.getStatus(), Orders.COMPLETED)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        if (Objects.equals(order.getPayStatus(), Orders.PAID)){
            //String refund = weChatPayUtil.refund(order.getNumber(), order.getNumber(), order.getAmount(), order.getAmount());
            String refund = String.valueOf(order.getAmount());
            order.setPayStatus(Orders.REFUND);
            log.info("退款:{}", refund);
        }
        order.setStatus(Orders.CANCELLED);
        order.setCancelReason(ordersCancelDTO.getCancelReason());
        order.setCancelTime(LocalDateTime.now());
        orderMapper.update(order);
    }

    @Override
    public void delivery(Long id) {
        //- 派送订单其实就是将订单状态修改为“派送中”
        //- 只有状态为“待派送”的订单可以执行派送订单操作
        Orders order = orderMapper.getById(id);
        if (order == null || !Objects.equals(order.getStatus(), Orders.CONFIRMED)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        Orders updateOrder = Orders.builder().id(id).status(Orders.DELIVERY_IN_PROGRESS).build();
        orderMapper.update(updateOrder);
    }

    @Override
    public void complete(Long id) {
        //- 完成订单其实就是将订单状态修改为“已完成”
        //- 只有状态为“派送中”的订单可以执行订单完成操作
        Orders order = orderMapper.getById(id);
        if (order == null || !Objects.equals(order.getStatus(), Orders.DELIVERY_IN_PROGRESS)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        Orders updateOrder = Orders.builder().id(id).status(Orders.COMPLETED).deliveryTime(LocalDateTime.now()).build();
        orderMapper.update(updateOrder);
    }

    @Override
    public void reminder(Long id) {
        Orders order = orderMapper.getById(id);
        if (order == null){
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        Map reminderMap = new HashMap();
        reminderMap.put(WebSocketServer.TYPE, WebSocketServer.REMINDER);
        reminderMap.put(WebSocketServer.ORDER_ID, id);
        reminderMap.put(WebSocketServer.CONTENT, "订单号:" + order.getNumber());
        //发送催单消息给客户端
        //为什么下单提醒和催单提醒功能要用websocket实现，因为websocket可以实现服务器主动向客户端发送消息
        //下单和催单是小程序客户端给服务器发送的请求，如果基于http请求，服务器只能向小程序响应数据，而不能主动向商家平台客户端发送消息
        webSocketServer.sendToAllClient(JSON.toJSONString(reminderMap));
    }
}
